# file:: database.rb
# author::  Ralph M. Churchill
# version::
# date::
#
# This source code copyright (C) 2006 by Ralph M. Churchill
# All rights reserved.
#
# Released under the terms of the GNU General Public License
# See LICENSE file for additional information.

# Array Based Database Class
# - Random Access
# - Enumerable
# - Searchable by Name
# - Can provide an "next open" id for Entity oids
# - Util Methods: size
# Assumption:
# - Used for storing objects of type Entity
# - Access is by Entity oid
# - Can contain nils, though their presence is hidden
# - Iterative operations will ignore nils
class VectorDatabase
    include Enumerable
    
    def initialize
        @container = Array.new
    end

    def each
        @container.compact.each{|e| yield e}
    end

    def find_by_name(name)
        @container.compact.find{|e| e.named?(name)}
    end

    def size
        @container.compact.size
    end

    def get(e_or_oid)
        return nil if e_or_oid.nil?
        e_or_oid.respond_to?(:oid) ? @container[e_or_oid.oid] : @container[e_or_oid]
    end

    # [+e+] Entity to add to the database
    # [+returns+] The object added (with an oid assigned)
    def add(e)
        raise "Can't store (#{e}) in (#{self})!" unless e.respond_to?(:oid)
        e.oid ||= find_open_id
        @container[e.oid] = e
        e
    end
private
    def find_open_id
        @container.empty? ? 0 : @container[size-1].oid + 1
    end
end

# Hash Based Database Class
# - Key'd access
# - Enumerable by values
# - Searchable by Name
# - Can provide an "next open" id for Entity oids
# - Util Methods: size
# Assumption:
# - Used for storing objects of type Entity
# - Key: Entity.oid, Value: Entity
class MapDatabase 
    include Enumerable
    def initialize
        @container = Hash.new
    end
    
    def each
        @container.values.each{|v| yield v}
    end

    def find_by_name(name)
        @container.values.find{|e| e.named?(name)}
    end

    def size
        @container.size
    end

    def get(e_or_oid)
        return nil if e_or_oid.nil?
        e_or_oid.respond_to?(:oid) ? @container[e_or_oid.oid] : @container[e_or_oid]
    end

    # [+e+] Entity to add to the database
    # [+returns+] The object added (with an oid assigned)
    def add(e)
        raise "Can't store (#{e}) in (#{self})!" unless e.respond_to?(:oid)
        e.oid ||= find_open_id
        @container[e.oid] = e
        e
    end
private
    def find_open_id
        if @container.empty? then
            1
        else
            @container.keys.max + 1
        end
    end
end

# Holds "entity templates" (which are Entities themselves) and
# instance of those templates. The instances transient over the course
# of the game. The templates will remain static -- unless a new template
# type is created and the database is reloaded.
#
# - Enumerable by instance
# - Searchable by template
# - Random/Key'd Access to instances
# - Instances:
#   - are of type Entity
#   - generated from a template or copies
#   - can be deleted (aka erased)
#   - can only be added by way of generation from template or copy
# - Templates:
#   - are loaded externally
# TODO 
#   - to make templates reloadable, will require re-checking instances
class TemplateInstanceDatabase
    include Enumerable

    def initialize(templates=[])
        @instances = MapDatabase.new
        @templates = VectorDatabase.new
        use_templates(templates)
    end

    def use_templates(templates,safe=true)
        templates.each{|t| @templates.add(t)}

        if safe
            valid_template_ids = @templates.map{|t| t.oid}
            # check to make sure all the instances have equivalent templates
            invalid_instances = @instances.reject do |inst|
                ! valid_template_ids.include?(inst.template)
            end
            # ########################### #
            # delete invalid_instances    #
            # ########################### #
        end
    end
    def each
        @instances.each{|i| yield i}
    end

    def find_template_by_name(template_name)
        @templates.find_by_name(template_name)
    end

    def get_template(template_id)
        @templates.get(template_id)
    end

    def get(instance)
        @instances.get(instance)
    end

    def generate_from_template(tmpl)
        raise "Cannot generate instance from #{tmpl}(#{tmpl.inspect})" \
            unless tmpl.respond_to?(:create_instance)
        inst = tmpl.create_instance
        @instances.add(inst)
        tmpl.apply_logic_to_instance(inst) if tmpl.respond_to?(:apply_logic_to_instance)
        inst
    end

    def generate_copy(inst)
        raise "Not a templated object!" unless inst.respond_to?(:template)
        raise "Invalid object for database!" unless @templates.get(inst.template)
        cp = inst.dup
        @instances.add(cp)
        cp
    end

    def erase(instance)
        @instances.erase(instance)
    end

    def templates
        @templates.dup
    end

    def add_instance(instance) #:nodoc:
        @instances.add(instance)
    end
end
